Where
-Infinity
0

Vendor Risk Score

See how open webui compares to other vendors in security performance

View Risk Score →
Severity
7.2
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L

Open WebUI from 0.8.6 before 0.11.4 contains a privilege escalation vulnerability in the terminal proxy that allows authenticated users to reach the Terminals administrator API with the shared key attached. Attackers can send requests through /api/v1/terminals/{serverid}/{path} to list or stop other users' terminals and modify terminal policies, injecting environment variables into other users' terminals.

First published (updated )

Autonomous AI agents running intrusions from recon to root. The operator wrote a goal and read the results. This blog covers real AI attacks happening in the wild.

\- 75 transcripts off attacker servers \- Claude Code on DeepSeek, pi, Codex \- command execution on 23 hosts \- SSRF to a root cloud box in 9h 44m \- unauth SQLi to OS commands \- Open WebUI rooted, CVE-2026-0766 \- one target held 5 days, 304 steps \- card and wallet data stolen, wiped \- 150,574 AI gateways swept for keys \- in house 0day dev on gateway source

First published (updated )
Social
reddit
Severity
5.1
XSS
AV:N/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:N

Open WebUI from 0.6.31 before 0.11.4 contains a stored cross-site scripting vulnerability in its Markdown renderer, which treats user-authored tool-call details blocks as tool output and mounts their embeds attribute in script-enabled iframes. Authenticated users can post crafted channel messages, chat messages, or citation content that runs JavaScript in viewers' browsers, enabling in-app phishing, or session hijacking where iframeSandboxAllowSameOrigin is enabled.

First published (updated )
Severity
7.1
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

Open WebUI from 0.9.0 before 0.11.4 contains a denial of service vulnerability that allows authenticated users with automations permission to stall the instance by submitting unsatisfiable recurrence rules. Attackers can repeatedly send rules like February 30th to the automations create or update endpoints, blocking the shared event loop for seconds per request.

First published (updated )
Severity
7.1
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

Open WebUI 0.11.0 before 0.11.4 contains a denial of service vulnerability in the automation recurrence-rule validator, whose sub-daily frequency guard parses rule text more strictly than the calendar library that executes it. Users permitted to create automations can submit whitespace-folded rules (or, in 0.11.0, lowercase frequencies or omitted RRULE prefixes) that expand per-second synchronously, blocking the shared event loop for minutes per request and on every scheduler poll.

First published (updated )
Severity
7.1
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

Open WebUI 0.11.0 before 0.11.4 compiles user-chosen grep patterns for its knowledge and chat file search tools with a regex engine that expands nested counted repetition unbounded at compile time and caches the result. Authenticated users can have the model call grepknowledgefiles with short nested-repetition patterns, blocking the event loop and exhausting worker memory.

First published (updated )
Severity
5.3
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N

Open WebUI before 0.11.4 contains a missing authorization vulnerability that allows authenticated users to reach disabled model provider backends by supplying an explicit backend index. Attackers can pass the index as a path segment or query parameter on Ollama and OpenAI-compatible listing routes to trigger requests carrying configured API keys, read version strings, and enumerate connections.

First published (updated )
Severity
7.1
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

Open WebUI from 0.6.42 before 0.11.4 contains an uncontrolled resource consumption vulnerability that allows authenticated users to exhaust server memory by supplying compressed remote image URLs in chat messages. Attackers can point the image field at a host returning gzip-encoded responses, which getimagebase64fromurl decompresses unbounded, causing denial of service for all users.

First published (updated )
Severity
5.3
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L

Open WebUI from 0.11.0 before 0.11.4 contains a regular expression denial of service vulnerability that allows authenticated users to stall server workers via file search patterns. Attackers can instruct the model to call grepknowledgefiles or grepchatfiles with a catastrophically backtracking regex, blocking the event loop for two seconds per repeated call.

First published (updated )
Severity
7.1
Infoleak
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

Open WebUI from 0.11.0 before 0.11.4 contains an information disclosure vulnerability that allows authenticated non-admin users with tool export permission to obtain tool source code shared with them read-only. Attackers can request GET /api/v1/tools/export to retrieve the full Python source of read-only shared tools, exposing embedded API keys and credentials.

First published (updated )
Severity
7.6
Infoleak
AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N

Open WebUI from 0.6.27 before 0.11.4 contains an information disclosure vulnerability that allows destination servers to obtain user session cookies by receiving the full browser cookie jar on OAuth-mode connections. Operators of OpenAI-compatible connections, OpenAPI tool servers or terminal servers can capture forwarded session cookies from routine requests and replay them to hijack accounts, including administrators.

First published (updated )
Severity
8.5
XSS
AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:N

Open WebUI from 0.6.33 before 0.11.4 contains a cross-site scripting vulnerability that allows authenticated users to execute script in victims' sessions by storing javascript: URLs in citation embedurl fields. Attackers can share a crafted chat whose citation, when clicked, is passed to window.open, letting them steal localStorage.token and act as the victim.

First published (updated )
Severity
5.3
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L

Open WebUI from 0.8.8 before 0.11.4 contains an improper authorization vulnerability that allows deactivated users to keep executing terminal commands through an already open terminal WebSocket. Because the terminal proxy route in terminals.py checks access only on the first message, deactivated users can keep running commands and reading or writing files on the terminal server.

First published (updated )
Severity
8.4
XSS
AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N

Open WebUI from 0.11.1 before 0.11.4 contains a stored cross-site scripting vulnerability in DocxPreview.svelte that allows authenticated users to execute script by uploading crafted DOCX attachments. Attackers can embed an OOXML altChunk HTML part or script-scheme hyperlink to steal a previewing administrator's session token from local storage and take over the instance.

First published (updated )
Severity
7.2
XSS
AV:N/AC:H/PR:L/UI:R/S:U/C:H/I:H/A:N

Open WebUI before 0.11.4 contains a stored cross-site scripting vulnerability in MarkdownInlineTokens.svelte, which renders markdown link URLs into anchor hrefs without validating their scheme. Authenticated users can embed javascript: links in shared chats that, when clicked by a victim, steal the victim's JWT from local storage, including admins' tokens.

First published (updated )
Severity
5.3
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N

Open WebUI from 0.9.6 before 0.11.4 contains an authorization flaw where the knowledge base file listing does not verify that a supplied directoryid belongs to the requested knowledge base. Users with read access to any knowledge base can supply a retained foreign directory UUID to GET /api/v1/knowledge/{id}/files. The returned breadcrumbs expose folder names, owner user ids and knowledge base ids of that directory and all its ancestors.

First published (updated )
Severity
8.6
AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N

Open WebUI from 0.7.0 before 0.11.4 contains a cross-origin message handling vulnerability in SyncStatsModal that allows any website to steal a signed-in user's session token by posting unvalidated verify:chat messages. Attackers can supply a path-traversal chat id redirecting the authenticated stats export request to the auths endpoint, receiving the victim's JWT for full API access.

First published (updated )
Severity
2.3
AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:L

Open WebUI from 0.6.0 before 0.11.4 contains an improper access control vulnerability in hasaccesstofile that lets former knowledge base collaborators keep file access via stale cached knowledge base references. Attackers with read or write grants can download removed files through the file content endpoint or delete them, including from the owner's private knowledge bases.

First published (updated )
Severity
5.3
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N

Open WebUI from 0.11.1 before 0.11.4 contains an authorization bypass in the OAuth token exchange endpoint, which reads role claims only from the provider's userinfo response and keeps the user's existing role when no roles claim is found, ignoring roles sent only in the ID token. Users with a linked account whose identity provider role no longer satisfies the OAuth role policy can send a valid provider access token to POST /api/v1/auths/oauth/{provider}/token/exchange to obtain a session retaining their previous role.

First published (updated )
Severity
5.3
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N

Open WebUI from 0.11.0 before 0.11.4 contains an improper authorization vulnerability in the chat fork handler, which places forked chats in the source chat's folder without checking folder write access. Shared folder members downgraded to read-only can call POST /api/v1/chats/{id}/fork on chats they created there while they had write access, inserting attacker-controlled chats visible to the folder owner and members.

First published (updated )

New blog: Hands Off the Keyboard

Autonomous AI agents running intrusions from recon to root. The operator wrote a goal and read the results. This blog covers real AI attacks happening in the wild.

\- 75 agent transcripts recovered from the attackers' own servers

\- Claude Code harness on DeepSeek, plus pi and Codex workers on other models

\- command execution on 23 hosts, most as root or SYSTEM

\- a single SSRF to cloud metadata to a new root instance in 9 hours 44 minutes

\- unauthenticated SQLi to OS commands on a domain joined SQL cluster

\- the target's own Open WebUI rooted through CVE-2026-0766

\- one target held for 5 days and 304 reasoning steps

\- card data and wallet keys stolen, then every trace deleted on command

\- 150,574 AI gateway resellers swept for upstream keys

First published (updated )
Social
reddit
Severity
6.5
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

Summary Chat histories are stored as an unvalidated JSON object. The walk that reconstructs a chat's message chain detected repeats using each message's own id field while moving through the history by map key, so a message that simply omitted id was never recorded as visited. A history whose messages referenced each other in a parent cycle therefore made the walk run forever. Any account with the default user role could store such a chat and trigger the walk, blocking the server for everyone.

Preconditions One account with the default user role. No administrator rights, no additional permissions, no configuration change and no non-default setting: creating a chat is available to every user out of the box. The attack runs entirely against the attacker's own chat, so no knowledge of any other user's data is needed. Instances where every account is trusted are affected in the sense that the fault is reachable, but require a user acting deliberately.

Impact The walk is synchronous and runs on the asyncio event loop, so while it spins, every request from every user is blocked, including unauthenticated /health and administrator endpoints. External health checks and orchestrator liveness probes fail alongside the UI. The list it appends to grows without bound, so a memory-capped deployment ends in an out-of-memory kill rather than a hang. The work is not cancelled when the client disconnects, so one fire-and-forget request is enough and the attacker can disconnect immediately. The malformed chat stays in the database, so restarting the process does not clear the condition: the next request that walks that chat hangs the new process, and recovery requires deleting the stored chat. No data is disclosed, altered or deleted.

Fix Fixed in 0.11.1 by https://github.com/open-webui/open-webui/commit/5c79ccc9e5c9efc2bc024d8f0b9757652ece929a. The walk now records the map key it is currently positioned at instead of the message's self-reported id, so it terminates after at most one step per stored message whatever the message contents are. Upgrading fully resolves the issue, including for chats stored while the deployment was on an affected version, which become harmless once the walk terminates.

Root cause Affected component: the message-chain reconstruction helper in backend/openwebui/utils/misc.py, reached from every path that rebuilds a chat's history, including chat completion, per-chat statistics, context compaction, subagents and timers. Affected setup: all builds from 0.5.0 up to and including 0.11.0.

The loop's visited set was keyed on the message body's id field while the loop itself advanced by looking the parent up as a key in the history map, so the two used different notions of identity. That id field is part of the stored chat object and is fully attacker-controlled, and the guard explicitly skipped recording it when absent, which left the exit condition unreachable for any message that omitted it. The write path does not validate the structure of a chat's history, so a history containing id-less messages in a parent cycle was persisted exactly as submitted.

Proof of concept As a default-role user on a running instance, store one chat of two messages that reference each other as parents, with the id field omitted:

json {"chat":{"title":"poc","history":{"currentId":"A","messages":{ "A":{"parentId":"B","role":"user","content":"a","childrenIds":[]}, "B":{"parentId":"A","role":"assistant","content":"b","childrenIds":[]}}}}}

POST /api/v1/chats/new stores it verbatim. A single subsequent GET /api/v1/chats/stats/usage as the same user then never returns. Sampled during the hang, the worker consumed one full CPU core and reached 1.86 GB resident within 60 seconds, still growing. Unauthenticated GET /health and administrator API calls both time out for as long as the process lives, and continue to do so after the attacker's connection closes. Restarting the server restores service until the first request that walks the stored chat, which hangs the new process the same way.

On 0.11.1 the same payload returns promptly, /health stays available throughout, and a well-formed chat still resolves its full history.

Credits @YashvantHange, who reported the missing-id cycle in the message-chain walk, demonstrated the resulting server-wide outage end to end against a live instance, and showed that it survives both attacker disconnect and a process restart.

1 / 2
Source: GitHub
First published (updated )
Severity
5
SSRF
AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:N/A:N

Summary

Open WebUI protects server-side web fetches with two controls: the operator's list of excluded hosts, and a check that refuses private and internal addresses. Neither control was applied to the destination of an HTTP redirect. On a deployment where redirect following is enabled, any authenticated user who can cause the server to fetch a URL could submit a page that redirects, and the server would fetch the redirect destination without either control being applied to it. The server therefore connects to hosts the operator excluded, and to internal addresses including loopback, private networks and cloud metadata endpoints.

Preconditions

AIOHTTPCLIENTALLOWREDIRECTS must be set to true. Its default is false, and on the default the affected fetch paths do not follow redirects at all, so a deployment that has not changed this setting is not affected.

The attacker needs an authenticated account of any role, with access to any feature that causes the server to fetch a URL. Web search, ingesting a URL into a collection, the built-in page fetch tool and image URLs in chat all reach it. No internal hostnames need to be known, because the loopback and cloud metadata addresses are fixed and identical on every deployment.

Neither ENABLELOCALWEBFETCH at its default of false nor any set of entries in WEBFETCHFILTERLIST prevents this.

Impact

An authenticated user can make the server issue requests to hosts the operator deliberately excluded, and to addresses on the internal network including loopback, private ranges and the cloud metadata endpoints of the major providers.

What reaches the attacker depends on which HTTP client the fetch path uses, and is stated here as verified rather than assumed. On the paths built on requests, the fetched body is returned to the caller, so the response of an excluded host is readable: the built-in page fetch tool hands it to the model, and the URL ingestion endpoint returns it in its response. Those paths kept a working private-address check at connection time, so what they reach is excluded public hosts and internal names, not private IP addresses. On the paths built on aiohttp, which are the ones that reach private addresses written as IP literals, fetched content reaches the web search response, a retrievable collection, or model input as base64 through chat image URLs. A path on that client returning an attacker-chosen response verbatim to the requester was not demonstrated.

The operator has no configuration that prevents this. 169.254.169.254 ships in the default excluded list, so an operator reviewing their configuration would reasonably conclude the metadata endpoint is out of reach. The documentation for the redirect setting further recommends the excluded-host list as a compensating control when enabling redirects, and that is precisely the control the redirect path skipped.

Fix

Fixed in 0.11.1 by https://github.com/open-webui/open-webui/pull/27823. The excluded-host list is now evaluated once per request on both HTTP clients, where the real destination is known and a reused connection cannot skip it, so it applies to every redirect hop. The private-address check stays at connection time and is now hooked where aiohttp resolves a host, which is also reached for hosts written as IP literals.

One case is not resolved by the upgrade alone. Where the server reaches the internet through a forward proxy, the private-address check sees the proxy's address rather than the final destination, so it does not constrain where the proxy is asked to connect. The excluded-host list is still applied to the destination on that path. Operators who route outbound fetches through a proxy should enforce destination restrictions on the proxy itself.

Root cause

Affected components:

- the web retrieval fetch paths, on both the aiohttp and requests clients - the built-in page fetch tool - the URL ingestion endpoint

Affected setup: builds that carry the redirect-following setting, which was introduced in 0.9.5. Earlier builds have no such setting.

The two controls sat at different layers, and each sat where only the originally submitted URL passes through. The excluded-host list was consulted inside URL validation, which runs once against what the user submitted, so a redirect destination never reached it on either client. The private-address check was placed inside the aiohttp resolver, and aiohttp answers a host written as an IP address itself without consulting a resolver, so for exactly those hosts the check never ran. Redirect following was added later as an option, and neither control was revisited at that point, so turning it on moved the real destination out of reach of both.

Proof of concept

Both vectors were reproduced against the shipped code of 0.11.0 and confirmed closed against 0.11.1, with AIOHTTPCLIENTALLOWREDIRECTS=true in both runs. A local HTTP server serves a redirect and a body, DNS is stubbed so the test hostnames resolve to loopback, and the guards themselves are unmodified.

Excluded-host list, redirect destination 203.0.113.1 excluded, submitted URL not excluded:

0.11.0 no check runs against the redirect destination, the server opens a connection to it 0.11.1 blocked at the redirect hop before any connection is attempted

Private-address check, host written as an IP literal, with ENABLELOCALWEBFETCH at its default of false:

0.11.0 fetch returned 'LOOPBACK-CONTENT-REACHED', no check ran 0.11.1 blocked, non-global address 127.0.0.1

Instrumenting 0.11.1 on a redirect that is allowed shows both controls running against the redirect destination as well as the submitted URL, and an ordinary public fetch is unaffected.

Credits

- @arpitjain099, original report: the excluded-host list is not re-applied to redirect destinations. - @Classic298: the private-address check is bypassed for redirect destinations written as IP literals, through aiohttp's resolver shortcut.

1 / 2
Source: GitHub
First published (updated )
Severity
7.1
SSRF
AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N

Summary

Open WebUI fetches user-supplied URLs on the server for RAG URL ingestion and web search, and screens the resolved addresses so internal destinations cannot be reached. That screen decided whether a destination was external by asking Python's standard library whether the address is globally routable. Several addresses reserved for internal use answer yes to that question, including 168.63.129.16, the Azure platform channel every Azure virtual machine can reach. Any authenticated user could therefore make the server issue requests to those addresses and read the responses back through the API.

Preconditions

- The affected paths are the server-side URL fetches: RAG URL ingestion and web search. Both require an authenticated, verified account. No administrator role and no special workspace permission are needed. - ENABLELOCALWEBFETCH must be at its default of false. Setting it to true disables the address screen by design, and internal destinations are reachable on purpose. - WEBFETCHFILTERLIST is empty by default in affected versions, so no operator-supplied block entry covered these addresses unless one was added by hand. - For the Azure platform channel specifically, the Open WebUI host must run on Azure (virtual machine, AKS, Container Apps or equivalent). That address is reachable from every Azure virtual machine regardless of network security group rules. On a host that is not on Azure the address routes nowhere and nothing is reachable through it.

Impact

An authenticated user could direct the server to issue GET requests at addresses reserved for internal use and receive the response body back in the API response, which also lands in the RAG context. On Azure that includes the platform channel, an endpoint the operator never intended to expose to application users. The same gap applied to the IPv4-translated range and to deprecated IPv6 site-local space, which on a host that routes them would reach internal services the same way.

What was observed is the reachability and the read-back. No specific credential or secret was retrieved from the Azure platform channel during this investigation, and this advisory does not claim one. Deployments not hosted on Azure are unaffected for the platform-channel address, and deployments running with local web fetch enabled were never protected by this screen in any version.

Fix

Fixed in https://github.com/open-webui/open-webui/pull/27823. Address screening no longer rests on the library's notion of a globally routable address alone: a default block list of reserved and internal-purpose ranges is applied to every resolved address, at URL validation and again at connection time on both HTTP transports, so it also covers redirect hops and DNS rebinding. Operators can extend the list through WEBFETCHFILTERLIST, which is merged with the defaults and cannot remove them.

Root cause

- backend/openwebui/retrieval/web/utils.py: the shared address screen used by every server-side fetch. - POST /api/v1/retrieval/process/web: RAG URL ingestion, returns the fetched body to the caller. - POST /api/v1/retrieval/process/web/search: web search, fetches each result.

The code ships in every build, so no optional component or feature flag limits which installations carry it.

The screen answered one question, whether an address is globally routable, and treated that answer as a proxy for whether the destination is external. Those are two different questions. The library classifies by IANA special-purpose registry membership, and the Azure platform channel is allocated out of ordinary public IPv4 space, so the library correctly reports it as global while in practice it is an internal endpoint of the host's own platform. Earlier hardening (GHSA-8x5v-cpv7-8jjp) extended the screen to unwrap IPv6 addresses that carry an IPv4 address inside them. That closed the encoded-address bypasses and left the underlying test unchanged, so an address that is public by registry and internal by convention still passed.

Proof of concept

As any verified user:

POST /api/v1/retrieval/process/web {"url": "http://168.63.129.16/?comp=versions"}

On an affected version the address passes validation, the request is issued, and the response body is returned in the content field of the API response.

The screening code of each released version was exercised directly:

- 0.11.0: 168.63.129.16, ::ffff:0:169.254.169.254 and fec0::1 all pass validation. - 0.11.1: all three are rejected, along with every alternative spelling of the same address (IPv4-mapped, IPv4-compatible, IPv4-translated, 6to4 and both NAT64 prefixes) and the obfuscated decimal, octal and hexadecimal forms. Ordinary public addresses continue to pass.

No request was issued to a live Azure platform channel. The read-back property was confirmed from the ingestion endpoint, which returns the fetched body to the caller in its response.

Credits

@NaorYaa reported that the Azure platform channel remained reachable after the previous hardening, and demonstrated the same gap for the IPv4-translated and IPv6 site-local ranges.

1 / 2
Source: GitHub
First published (updated )
Severity
7.1
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L

Summary External knowledge connections are created and owned by administrators, and are shared by every external knowledge base bound to them. Deleting an external knowledge base also removed that connection from the instance configuration, with no check on the caller's role and no check for other knowledge bases still using it. Any authenticated user holding a write grant on a single external knowledge base could therefore wipe a shared connection that other knowledge bases depend on, and the dedicated administrator route for deleting a connection explicitly refuses that same operation while the connection is still in use.

Preconditions The deployment uses external knowledge bases, backed by the qdrant, milvus or pgvector external providers. Instances with only local knowledge bases are not affected.

An administrator created at least one external connection and at least one external knowledge base bound to it. Both of those actions are admin-only.

The attacker is an ordinary authenticated user holding a write grant on one of those external knowledge bases. A read grant is not enough, and an unrelated user cannot reach the route at all.

The damage scales with sharing. It is worst when one connection backs several knowledge bases, because deleting a single knowledge base destroys the connection for all of them.

Impact An ordinary user removes instance-wide configuration that only administrators can create or manage. Every other external knowledge base bound to the deleted connection keeps its stored connection id and stops working: retrieval against it fails with "External knowledge connection not found", so those knowledge bases return nothing in chat until an administrator recreates the connection by hand. The stored credential goes with it, and connection API responses strip credentials, so an administrator who did not keep the key elsewhere cannot restore the connection without obtaining it again.

Nothing is disclosed to the attacker and the external vector store itself is untouched. What is lost is Open WebUI's own connection configuration, and the availability of the knowledge bases that depend on it.

Fix Fixed in 0.11.1 by https://github.com/open-webui/open-webui/pull/28113. Deleting an external knowledge base now clears its connection only when the caller is an administrator and the knowledge base being deleted is the last one referencing that connection, matching the dedicated connection delete route. Upgrading resolves the issue with no configuration change required.

Root cause Affected component: the knowledge base delete handler in backend/openwebui/routers/knowledge.py, reached through DELETE /api/v1/knowledge/{id}/delete.

Affected setup: every release from 0.10.0, where external knowledge connections were introduced, up to and including 0.11.0.

The handler authorized the caller against the knowledge base, which a write grant satisfies, and then performed a second, unrelated operation against global configuration in the same request. That second step carried no authorization of its own. It neither required the administrator role that creating a connection requires, nor counted the other knowledge bases still bound to the connection. The dedicated route DELETE /api/v1/knowledge/external/connections/{id} already performed both checks, so an outcome an administrator was blocked from was reachable by a non-administrator through the knowledge base route.

Proof of concept Reproduced against a local instance on 0.11.0 with default settings, and against 0.11.1 for comparison.

An administrator created one external connection and two external knowledge bases bound to it, then granted a plain user write access on the first knowledge base. As that user:

DELETE /api/v1/knowledge/{KBA}/delete Authorization: Bearer <user token>

On 0.11.0 the request returns 200 true. The administrator connection list then returns zero connections, the second knowledge base still carries the deleted connection id, and retrieving from it raises "External knowledge connection not found".

On 0.11.1 the same request deletes the knowledge base, the connection list still returns the connection, and the second knowledge base is unaffected. An administrator deleting the last remaining knowledge base on that connection still clears it, so the intended cleanup is preserved.

Credits @Bellingham-max, for reporting the missing authorization on the shared connection teardown.

1 / 2
Source: GitHub
First published (updated )
Severity
4.3
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N

Summary The chat-completions endpoint reads a folder id out of the request body and saves the newly created chat into that folder without checking that the caller is allowed to write there. Any authenticated user who knows a folder's id can put a chat of their own into another user's folder, including a shared folder where they hold read-only access and a folder they have no access to at all. The chat then shows up in that folder for everyone who can read it, under a title and with content the attacker controls. The dedicated chat-creation and chat-move endpoints already enforced this check, the chat-completions path did not.

Preconditions - An authenticated account of any role. No elevated permission, no admin involvement. - Folders enabled, which is the default (ENABLEFOLDERS=true, USERPERMISSIONSFEATURESFOLDERS=true). - The attacker needs the target folder's id. A member of a shared folder gets it from the shared-folder listing. Sharing a folder with named users is available to ordinary users by default; only wildcard public sharing is gated. A folder that was never shared has an id the attacker cannot obtain through any endpoint available to them, so those folders are not practically reachable. - Releases before 0.10.0 contain the same missing check but have no read path that lists a folder's chats across owners, so the injected row was never visible to anyone.

Impact The integrity of folder contents. An attacker who cannot write to a folder can place chats into it, and every member with read access, the owner included, sees the entry with the attacker's display name and a title the attacker can change to arbitrary text at any time. Members can also open the injected chat and read its messages. In a workspace where a shared folder is treated as trusted, that is a usable surface for planting misleading or phishing content under another team's nose.

Nothing is disclosed to the attacker and nothing existing is altered. Writing into a folder grants no read access to it, so the attacker still cannot list or open the folder's other chats, and no data belonging to other users can be modified or deleted through this path. Availability is unaffected.

Fix Fixed in 0.11.1 by https://github.com/open-webui/open-webui/pull/28366. Chat creation, chat moves and the chat-completions creation path now share a single folder write-access check, so a folder id the caller cannot write to is rejected everywhere a chat's folder can be set. Upgrading to 0.11.1 resolves it completely, with no configuration change required.

Root cause Affected component: the chat-completions handler in backend/openwebui/main.py, reached through POST /api/chat/completions and POST /api/v1/chat/completions. Affected setup: every deployment on 0.10.0 through 0.11.0 with folders enabled.

The completions handler gained its own chat-creation branch, which copied the client-supplied folder id into the chat it saved. The ownership and shared-write check lived as duplicated inline code inside the two chat routers rather than in one shared helper, so a third place that learned to set a folder id inherited none of it. Separately, folder listings had been changed to return a folder's chats across all owners so that shared folders work at all, which turned a write that used to be invisible to everyone into one the whole folder can see.

Proof of concept Reproduced on a running instance at 0.11.0 with three accounts: one administrator and two ordinary users, one acting as victim and one as attacker.

1. The victim creates a folder. The attacker is given no access to it: reading the folder returns 404 and listing its chats returns 403. 2. The attacker sends POST /api/v1/chats/new with the victim's folder id. Rejected with 404, which is the pre-existing guard working. 3. The attacker sends POST /api/chat/completions with parentid: null, no chat id, and the victim's folder id in the body. Accepted. 4. The victim lists their own folder and sees a chat owned by "Attacker". The victim can open it and read its messages. The attacker then renames their own chat and the new title appears verbatim in the victim's folder listing.

Repeating step 3 with the attacker granted read-only access to a shared folder also succeeds on 0.11.0. On 0.11.1 both variants are rejected with 404 and the folder stays empty, while a member holding write access can still create chats there as intended.

The three accounts and the folder were created through the normal API. Nothing was written directly to the database.

Credits whyiug, for reporting the missing folder write-access check on the chat-completions creation path.

1 / 2
Source: GitHub
First published (updated )
Severity
8.7
XSS
AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N

Summary Any authenticated user with access to a shared terminal server could get script of their choosing to run in the Open WebUI origin itself. The in-app port preview rendered the content of a previewed port in an iframe whose sandbox always granted allow-same-origin alongside allow-scripts, and that content is served from a path on the application's own origin, so the sandbox provided no isolation at all. Script served on a previewed port could read the victim's session token and take over the account.

Preconditions - At least one terminal server configured by an admin (TERMINALSERVERCONNECTIONS, empty by default) and reachable by both the attacker and the victim. Deployments with no terminal server configured are not affected. - The attacker needs a normal authenticated account with access to that terminal connection, no admin rights, plus the ability to start a process listening on a port there. - Personal terminals a user configures for themselves are not affected. Those carry an external URL, so the previewed document is cross-origin and the sandbox held. - The victim has to open the port list and click the attacker's port. - TERMINALPROXYHEADERS unset and no CONTENTSECURITYPOLICY set, which are the defaults. An operator who had already set a restrictive Content-Security-Policy through either was not exposed, since those headers apply to the proxied response. - The iframeSandboxAllowSameOrigin user setting is off by default, but the affected branch ignored it entirely.

Impact The previewed page runs in the application origin, so it can reach the parent window, read the session token out of localStorage and exfiltrate it, which is full account takeover of the victim. If the victim is an admin, or any user holding workspace.functions, that takeover extends to server-side code execution through Functions. Serving the page costs the attacker nothing beyond the terminal access they already hold, so the only real barrier is getting the victim to open that port. Instances with no terminal server configured were never affected, and neither were personal terminals pointed at an external URL.

Fix Fixed in 0.11.1 by 54d7a2237. The port preview now gates allow-same-origin behind a terminalPreviewAllowSameOrigin user setting that is off by default, so the preview loads at an opaque origin and cannot reach the parent context. Upgrading is sufficient and no configuration change is required. Previews of pages that need same-origin access still work if a user turns that setting on, and doing so re-enables the behaviour described here for that user.

Root cause - src/lib/components/chat/FileNav/PortPreview.svelte, the preview iframe, reached by clicking a port in the file navigator's port list. - Present from 0.8.11, the first release containing the component, through 0.11.0.

Every other iframe in the application had already been moved to an opt-in same-origin setting. The port preview kept a static sandbox string with allow-same-origin baked into it, and it was missed when the equivalent problem was fixed in the file preview for 0.11.0. Because the terminal proxy is mounted under the application's own origin and forwards the upstream response and its content type without adding a Content-Security-Policy of its own unless the operator configured one, and no global CSP is set, the sandbox was the only isolation boundary left, and it was granting precisely the permission that dissolved it.

Proof of concept On a terminal server the victim can also reach, serve an HTML page containing a script that reads window.parent.localStorage.token and sends it to a host the attacker controls. The victim opens the file navigator, switches to the port list and clicks that port. The preview opens, the script executes at the application origin, and the token is exfiltrated.

Credits Reported by @manus-use (researcher zx / Jace).

1 / 2
Source: GitHub
First published (updated )
Severity
4.3
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N

Summary Any member of a channel who can post to it could also replace the text of a message written by a different member. The channel branch of the chat completions endpoint checked that the caller may write to the channel, and that the targeted message belongs to that channel, but never checked that the caller actually wrote the message being edited. The message kept its original author, so the replacement text was displayed and stored as though the victim had written it. The dedicated channel message edit route performed the authorship check correctly and refused the same edit, so the two paths disagreed about who may modify a message.

Preconditions - Channels must be enabled. ENABLECHANNELS defaults to False, so a default deployment is not affected until an administrator turns channels on. - The attacker must be an approved user holding write access to the target channel, which is the ordinary state for any participant. No elevated role, group membership or additional permission is needed, and the attacker does not need to be the channel owner. - The attacker must know the id of the targeted message. That id is returned to every member who reads the channel, so it requires no further access. - Channels the attacker cannot post to are not affected, and neither are deployments with channels disabled.

Impact An attacker can rewrite what another member said, under that member's name, in a shared channel. The stored record and every later reader show the victim as the author of text they never wrote, so this is a loss of both integrity and non-repudiation: the victim cannot point to the record to show what they actually said. A conversation can be silently altered after the fact rather than only appended to.

How much control the attacker has over the replacement text depends on the configured model, because the text is produced by generating a completion into the victim's message. Reducing a message to unrelated or empty content works regardless of the model, while reproducing an exact attacker-chosen string requires a model that returns the supplied prompt closely enough. The attack grants no new role or permission, does not reach channels the attacker cannot already post to, and exposes no message content they could not already read.

Fix Fixed in 0.11.1 by commit 7d392bedc (#28631). The gate on the channel branch now also compares the targeted message's author against the calling user and refuses when they differ, which is the condition the dedicated channel message update route already applied. Administrators retain the ability to edit any message, on both paths, as before. Upgrading to 0.11.1 fully resolves this, and no configuration change is required.

Root cause Affected components: - the channel branch of the chat completions handler, which resolves a client-supplied message id for editing - the channel event emitter, which re-checks the message before persisting the update

Not affected: the dedicated channel message update route, which already held the correct check.

The channel branch was written to answer the question of whether the caller may write into this channel and whether the message belongs to it. Both of those checks were present and effective. The question it never asked was whether the caller wrote the message. Ownership of a channel message is carried by the message record rather than by the channel, so channel-level write access was treated as sufficient authority over every message inside it. The dedicated edit route grew the authorship comparison because editing is its only purpose, while the completions path reached the same operation through a general-purpose entry point and inherited only the coarser channel check.

Proof of concept Reproduced against a running instance built from the development branch, with channels enabled and an OpenAI-compatible upstream that echoes the submitted prompt so the replacement text is deterministic.

Setup, entirely through the ordinary API: an administrator creates two further accounts, Alice and Mallory, both with the plain user role and no additional permissions, creates a channel, and grants both accounts read and write access. Alice posts a message reading ALICE ORIGINAL MESSAGE. Nothing is written directly to the database.

Control, through the dedicated route:

POST /api/v1/channels/<channelid>/messages/<alicemessageid>/update Authorization: Bearer <mallorytoken> {"content": "PWNEDBYMALLORY"} -> 403

Attack, submitting the same target id to the completions endpoint:

POST /api/chat/completions Authorization: Bearer <mallorytoken> {"model": "<model>", "stream": true, "chatid": "channel:<channelid>", "id": "<alicemessageid>", "messages": [{"role": "user", "content": "PWNEDBYMALLORY"}]} -> 200

Reading the channel back afterwards shows the targeted message content replaced with the attacker's string while its recorded author is still Alice.

Credits - @Classic298 — identified that the completions path and the dedicated edit route disagreed on message authorship, and reproduced the cross-member overwrite end to end.

1 / 2
Source: GitHub
First published (updated )
Severity
4.3
Infoleak
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N

Summary The built-in knowledge search tool returns knowledge bases the calling user has no access to. The tool works out which knowledge bases the caller may read and hands that set to the vector store as a search filter, and that filter is the only access control on the path. Most of the shipped vector backends accept the filter argument on their search method and never apply it, so the search returns matches from every knowledge base in the shared collection. The sibling query method on those same backends does apply a filter, which is why the omission is invisible from the calling code.

Preconditions - The deployment must use one of the affected vector backends, selected with VECTORDB, which defaults to chroma. Chroma applies the filter, so a default deployment is not affected. - Eleven of the fifteen shipped backends ignore the filter: both Qdrant clients, Elasticsearch, OpenSearch, both Milvus clients, openGauss, Oracle 23ai, Pinecone, S3 Vectors and Weaviate. Chroma, pgvector, MariaDB and Valkey apply it and were never affected. - The caller needs a model that can call tools, with the knowledge built-in tools left enabled, which is the default, and with no knowledge attached to the model itself. No elevated role or permission is required. - At least one knowledge base must exist that the caller cannot otherwise read.

Impact A user receives the identifier, name and description of knowledge bases that were never shared with them, and chooses how many results to ask for, so the set can be enumerated by varying the query. The affected collection stores one entry per knowledge base whose text is its name and description, so where those values are themselves sensitive, for example when they name a customer, a project or an investigation, that disclosure is the loss.

The exposure is confined to that metadata. Document text lives in separate per-knowledge-base collections reached by a different call that is scoped by collection rather than by this filter, so stored documents are not returned by this path, and no write access is gained. Because the default backend is unaffected, the population at risk is operators who deliberately moved to an external vector store, which in practice means larger deployments.

Fix Fixed in 0.11.1 by 1d6d4e6e6. Every affected backend now applies the caller-supplied filter in its search method, combined with the collection or tenant scoping that method already performed. Where a backend's filter builder could express only single-value equality it was extended to express set membership, since that is the form the knowledge tool sends, and a filter using any other operator is now rejected rather than dropped. Upgrading is sufficient and no configuration change is required. Deployments on Chroma, pgvector, MariaDB or Valkey were never affected and need no action.

Root cause - The search method of each affected vector client, which declares a filter parameter and never references it. - The built-in knowledge search tool, which relies on that filter as its only access check. - Present from 0.7.0, the release that introduced the tool, through 0.11.0.

Each client exposes a search method and a query method that appear interchangeable from the calling side and differ in whether the caller's filter survives. Search built its request with collection or tenant scoping only, so it was correctly scoped to the collection and entirely unscoped within it. The knowledge tool resolves the caller's readable knowledge bases correctly and passes them down, then reads the results without rechecking them, on the reasonable assumption that a filter handed to a vector store is applied. Because the access decision was delegated wholly to a parameter that most implementations discarded, the caller had no way to observe that it received more than it asked for.

That the same omission repeated across most backends points at the shared interface rather than at any one client: the base class defines the parameter without obliging an implementation to honour it, and each backend was written against the interface independently.

Proof of concept Run against the shipped client classes taken from the 0.11.0 and 0.11.3 release tags, with a real Qdrant engine behind them. Two entries were inserted into the shared knowledge base collection, one readable and one not, mirroring the payload the application writes. The entries were inserted directly rather than created through the application.

On 0.11.0, search given a filter naming only the readable knowledge base returned both, and the same call with no filter returned an identical result set, confirming the filter had no effect:

search(filter={'knowledgebaseid': {'$in': ['kb-allowed']}}) -> ['kb-allowed', 'kb-secret'] search(filter=None) -> ['kb-allowed', 'kb-secret']

On 0.11.3 the same filter returned only the readable entry, while the unfiltered control still returned both, confirming the collection held both and the filter is what excluded the second:

search(filter={'knowledgebaseid': {'$in': ['kb-allowed']}}) -> ['kb-allowed'] search(filter=None) -> ['kb-allowed', 'kb-secret']

The Chroma client, run the same way on both tags, returned only the readable entry in every case, which is the default backend being unaffected. The S3 Vectors client was additionally run against a stub that answers every query with the whole index regardless of the filter it is sent: on 0.11.0 it returned both entries, and on 0.11.3 it returned only the readable one.

The remaining backends were checked by driving each patched filter builder with the exact filter the knowledge tool sends and confirming the request it produces restricts to the readable knowledge base.

Credits Reported by @Classic298.

1 / 2
Source: GitHub
First published (updated )
Severity
8.1
SQL Injection
AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

Summary

On SQLite deployments, the lookup that maps an external identity to a local account does a substring match instead of an exact match. A subject value containing SQL wildcard characters therefore matches accounts the value was never issued for, and the sign-in binds to whichever account the database returns first, which can be an administrator. The same defect affects SCIM external-ID resolution. PostgreSQL deployments are not affected, because they take a separate and correct code path.

Preconditions

The database is SQLite. This is the default backend. PostgreSQL deployments are not affected at all. OAuth or OIDC sign-in is configured, or SCIM provisioning is enabled. Both are off by default. For an attacker to steer the match deliberately, they must control the value of the claim Open WebUI uses as the subject. That value is normally assigned by the identity provider and is not attacker-controlled: the shipped GitHub and Feishu configurations use provider-assigned numeric identifiers, and a standard OIDC sub is provider-assigned. The deliberate case therefore requires an operator to have pointed OAUTHSUBCLAIM at a claim the end user can set at the identity provider, such as a username or email claim, or an identity provider that lets a user choose their own subject value. No attacker and no misconfiguration are needed for the accidental case. A legitimate subject value that happens to contain an underscore matches other accounts as well, and which account is returned depends on database row order. For the SCIM path, the caller must already hold the SCIM bearer token, which is a privileged credential.

Impact

A sign-in can be bound to an account other than the one the identity provider authenticated. Where the operator has made the subject claim user-settable, an attacker who registers at that provider can choose a value that matches an existing account and receive a session for it, including an administrator account, which is a full compromise of the instance. Where the subject claim is provider-assigned, the deliberate attack is not available, and what remains is a correctness failure in which an ordinary subject value containing an underscore can resolve to the wrong account and hand one user another user's session non-deterministically.

The defect is in the identity match itself, so it is not mitigated by any downstream permission check: by the time a session is issued the wrong account has already been selected. It does not allow account creation, and it does not affect password sign-in, PostgreSQL deployments, or any deployment with OAuth, OIDC and SCIM all disabled.

Fix

Fixed in 0.11.1 by https://github.com/open-webui/open-webui/pull/28624. The two identity lookups now compare the nested JSON value directly through SQLAlchemy's JSON subscript operator, which emits an exact match on both supported databases, instead of going through the column-level contains() operator that degraded to a substring comparison on SQLite. The hand-written per-dialect branching is removed, since the operator already handles both backends.

Upgrading fully resolves it and no configuration change is required. Existing stored identities are unaffected, as the stored format does not change.

Root cause

backend/openwebui/models/users.py — getuserbyoauthsub, resolves an OAuth or OIDC identity to a local account. backend/openwebui/models/users.py — getuserbyscimexternalid, the same pattern for SCIM. Reached from the OAuth callback handler and the OAuth token-exchange handler, and from the SCIM user routes. Affects builds running on SQLite, which is the default database.

The oauth and scim columns are declared with SQLAlchemy's generic JSON type. That type does not implement a containment comparator, so a contains() call against it falls back to the generic string operator and compiles to a LIKE with the operand wrapped in % on both sides. The intent was a JSON containment test; what was emitted was a substring test against the serialized JSON, in which % and carry their usual LIKE meaning. The PostgreSQL branch was written separately against JSONB with an equality comparison and is correct, which is why the defect is confined to the default backend and why it survived review: the two branches look symmetrical and only one of them does what it appears to do.

Proof of concept

Against a SQLite instance with an OIDC provider configured, two accounts exist: an administrator whose stored subject is adminsub9999, and an ordinary user whose stored subject is bobsub1234. Both rows were seeded directly rather than created through a live provider sign-in; the lookup under test was then called as the application calls it.

Resolving the subject value % returns the administrator account. Resolving adminsub% likewise returns the administrator account. Resolving the correct full values returns the correct accounts, and resolving an unknown value returns nothing, so the failure is visible only when the supplied value contains a wildcard character. A subsequent check with an ordinary subject value containing an underscore showed it matching more than one account, with the returned account determined by row order.

Credits

Reported by @Classic298.

1 / 2
Source: GitHub
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203